/*
 * Copyright (C) 2012 haru Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harusource.di.core;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import java.util.Set;

import com.harusource.di.delegate.Delegate;
import com.harusource.di.exception.ViewNotFoundException;
import com.harusource.di.mapper.AnnotaionMapper;

import dalvik.system.DexFile;

import android.content.Context;
import android.util.SparseArray;
import android.view.View;

public class HaruCore {
    private static final int OPTIMIZED_SIZE1 = 200;
    
    private static final int OPTIMIZED_SIZE2 = 5;

    public static final String ERROR_MSG1 = "[ERROR @Event][Method = %s] You must specifiy valid resource id. It couldn't find out specified view.";

    public static final String ERROR_MSG2 = "@Event annotation must be specified the resource id.";

    public static final String ERROR_MSG3 = "A implementation of Delegate has to have a method with @EventHanlder.";

    public static final String ERROR_MSG4 = "The specified clazz is not a implementation of Delegate.";

    public static final String ERROR_MSG5 = "[ERROR @Inject][Field = %s] You must specifiy valid resource id.";

    public static final String ERROR_MSG6 = "[ERROR @Inject][Feild = %s] You can not specify multi-resource id to none array field.";

    public static final String ERROR_MSG7 = "[ERROR @Inject][Feild = %s] Unsupported field type.";

    public static final String ERROR_MSG8 = "[ERROR @Inject][Field = %s] Invalid relationship between specified resource id and field type.";

    public static final String METHOD_FINDVIEWBYID = "findViewById";

    public static final int DEFAULT_RESOURCEID = 0;

    public static final String RESOURCE_COLOR = "color";

    public static final String RESOURCE_ARRAY = "array";

    public static final String RESOURCE_STRING = "string";

    public static final String RESOURCE_BOOL = "bool";

    private static Map<Class<?>, AnnotaionMapper> mMapper = null;
    
    private HaruCore(){}

    synchronized public static void init(Context context) {
        // Check if already initialized
        if (mMapper != null)
            return;

        try {
            // Make a class cache from DexFile
            Set<Class<?>> classes = new HashSet<Class<?>>(OPTIMIZED_SIZE1);
            Context app = context.getApplicationContext();
            DexFile dexFile = new DexFile(new File(app.getApplicationInfo().sourceDir));
            Enumeration<String> entry = dexFile.entries();

            while (entry != null && entry.hasMoreElements()) {
                try {
                    classes.add(Class.forName(entry.nextElement()));
                } catch (ClassNotFoundException e) {
                    // As some classes in support-v4 will course
                    // ClassNotFoundException,
                    // this exceptions are ignored
                }
            }

            // Load AnnotaionMappers from the class cache
            mMapper = new HashMap<Class<?>, AnnotaionMapper>(OPTIMIZED_SIZE2);
            for (Class<?> clazz : classes) {
                // Check if the class implements AnnotationMapper
                if (isInterfaceImpl(clazz, AnnotaionMapper.class)) {
                    // Add mapper instance to mMapper
                    AnnotaionMapper mapper = (AnnotaionMapper) clazz.newInstance();
                    mMapper.put(mapper.getAnnotaion(), mapper);
                }
            }

            // Call AnnotaionMapper.setup for a preparation
            for (AnnotaionMapper mapper : mMapper.values()) {
                mapper.setup(classes);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void injectFeild(Context context, Object target) {

        /** Field Injection **/
        for (Field field : target.getClass().getDeclaredFields()) {
            for (Annotation annotation : field.getDeclaredAnnotations()) {
                AnnotaionMapper mapper = mMapper.get(annotation.annotationType());
                if (mapper != null) {
                    mapper.inject(context, annotation, target, field, null);
                }
            }
        }
    }

    public static void injectEvent(Context context, Object target) {
        Object injeced_cache = new SparseArray<Map<Class<?>, Delegate>>();

        for (Method method : target.getClass().getDeclaredMethods()) {
            Annotation[] annotaions = method.getDeclaredAnnotations();
            for (Annotation annotation : annotaions) {
                AnnotaionMapper mapper = mMapper.get(annotation.annotationType());
                if (mapper != null) {
                    mapper.inject(context, annotation, target, method, injeced_cache);
                }
            }
        }
    }

    public static boolean isInterfaceImpl(Class<?> _class, Class<?> _interface) {
        // Check if the arguments are not null
        if (_class == null || _interface == null) {
            return false;
        }

        // Check if the class implements specified interface
        if (!_class.isInterface() && _interface.isAssignableFrom(_class)
                && !Modifier.isAbstract(_class.getModifiers())) {
            return true;
        }
        return false;
    }

    public static boolean compareMethod(Method method1, Method method2) {
        // Check if the arguments are not null
        if (method1 == null || method2 == null)
            return false;

        // Check if each return value types are same
        if (method1.getReturnType() != method2.getReturnType())
            return false;

        // Get argument types
        Class<?>[] params1 = method1.getParameterTypes();
        Class<?>[] params2 = method2.getParameterTypes();

        // Check a number of arguments
        if (params1.length != params2.length)
            return false;

        // Check if each arguments are completely same
        int i = 0;
        for (i = 0; i < params1.length; i++) {
            if (params1[i] != params2[i])
                return false;
        }

        // method1 and method2 are same method type
        return true;
    }

    public static View findViewById(Object target, int id) throws ViewNotFoundException {
        View view = null;
        try {
            Method findViewById = target.getClass().getMethod(METHOD_FINDVIEWBYID, int.class);
            view = (View) findViewById.invoke(target, id);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        if (view != null)
            return view;
        else
            throw new ViewNotFoundException();
    }
}
